package pl.pg.misra.Helpers;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.openide.util.Exceptions;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import pl.pg.misra.AST.AST;
import pl.pg.misra.Interfaces.ISourceParser;

/**
 *
 * @author Paweł Olczak
 */
public class SourceParser implements ISourceParser {

    List<AST> asts;

    final File currentDir = new File(System.getProperty("user.dir"));
    final String PARAMETER_LINE = "@";
    final String COMPOUND_STATEMENT = "CompoundStmt";
    final String COMMAND;
    final String BRACKET_WITH_QUOTE = "(.*?)((\\)\"*)+$)";
    final String CLOSING_BRACKET = "\\)";
    final String OPENING_BRACKET = "\\(";
    final String AST_FILE_EXTENSION = ".dump";
    final int SPLIT_WITH_EMPTY_ENTRIES = -1;
    final FileFilter sourceFileFilter = new FileFilter() {

        @Override
        public boolean accept(File file) {
            return file.isDirectory() || file.getName().endsWith(".c");
        }
    };
    final FileFilter treeFileFilter = new FileFilter() {

        @Override
        public boolean accept(File file) {
            return file.getName().endsWith(AST_FILE_EXTENSION);
        }
    };

    public SourceParser() {
        asts = new ArrayList<>();
        
        COMMAND = loadCommand();
    }

    @Override
    public List<File> buildASTFiles(File source) {
        String files = "";
        String[] filesNames;
        List<File> astFiles = new ArrayList<>();
        
        File[] treeFiles = currentDir.listFiles(treeFileFilter);
        
        for (File treeFile : treeFiles){
            boolean result = treeFile.delete();
        }

        filesNames = getFiles(source, files).split(" ");

        for (String file : filesNames) {
            File newFile = new File(file);

            astFiles.add(newFile);

            file = file.replaceAll("\\\\", "/");
            file = file.replaceFirst(":", "/");
            file = "/cygdrive/" + file;

            String command = COMMAND + file + " > ./" + newFile.getName() + AST_FILE_EXTENSION;

            try {
                Process p = Runtime.getRuntime().exec(command);
                try {
                    p.waitFor();
                    
                } catch (InterruptedException ex) {
                    Exceptions.printStackTrace(ex);
                }
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            }
        }

        return astFiles;
    }

    @Override
    public List<AST> parseToAST(List<File> sourceFiles) {
        List<File> astFiles = new ArrayList<>();
        
        asts.clear();

        for (File f : sourceFiles) {
            File astFile = new File(f.getName() + AST_FILE_EXTENSION);
            
            if (astFile != null){
                createASTTrees(astFile.getAbsolutePath(), f.getAbsolutePath());
            }

            astFiles.add(astFile);
        }
        
        for (File f : astFiles){
            boolean result = f.delete();
        }

        return asts;
    }

    private void createASTTrees(String astPath, String sourcePath) {
        try {
            String line;
            String firstLineFromSource;
            AST currentNode = null;
            boolean doNotSkip = false;

            firstLineFromSource = getFirstLineFromSource(sourcePath);

            FileInputStream astFileInput = new FileInputStream(astPath);

            DataInputStream astDataInput = new DataInputStream(astFileInput);

            BufferedReader astReader = new BufferedReader(new InputStreamReader(astDataInput));

            while ((line = astReader.readLine()) != null) {
                line = line.replaceAll("\\s+", " ");
                line = line.replaceFirst("^ ", "");

                if (line.startsWith(firstLineFromSource)) {
                    doNotSkip = true;
                }

                if (doNotSkip) {
                    if (isFunctionLine(line)) {
                        currentNode = loadFunctionNode(line, sourcePath);
                    } else if (isStatementLine(line)) {
                        currentNode = loadStatementNode(line, currentNode);
                    } else if (isAdditionalNodeDataLine(line)) {
                        currentNode = loadAdditionalData(line, currentNode);
                    } else if (containsClosingBracket(line)) {
                        currentNode = finalizeNode(line, currentNode);
                    }
                }
            }
            
            astReader.close();
            astDataInput.close();
            astFileInput.close();     
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

    }

    private AST finalizeNode(String line, AST node) {
        AST currentNode = node;
        currentNode = getCurrentNode(line, currentNode);

        return currentNode;
    }

    private AST loadAdditionalData(String line, AST node) {
        AST currentNode = node;

        String[] attributesArray = line.split(" ");

        loadStatementAttributes(node, attributesArray);

        if (line.endsWith(")") || line.endsWith("\"")) {
            currentNode = getCurrentNode(line, currentNode);
        }

        return currentNode;
    }

    private AST loadStatementNode(String line, AST parentNode) {
        AST node = new AST();
        AST currentNode = node;

        String attributes = line.split(">", -1)[1];

        if (!attributes.isEmpty()) {
            String[] attributesArray = attributes.split(" ");

            loadStatementAttributes(node, attributesArray);
        }

        node.setNodeType(line.split(" ")[0].replaceFirst(OPENING_BRACKET, ""));
        
        Pattern pattern = Pattern.compile("(.*?)(line:\\d+:\\d+)(.*)");
        Matcher matcher = pattern.matcher(line);
        
        if (matcher.find())
        {
            node.setLine(matcher.group(2));
        }

        node.setParent(parentNode);
        parentNode.getChildrens().add(node);

        if (line.endsWith(")") || line.endsWith("\"")) {
            currentNode = getCurrentNode(line, currentNode);
        }

        return currentNode;
    }

    private AST getCurrentNode(String line, AST currentNode) {
        String closingBrackets = line.replaceAll(BRACKET_WITH_QUOTE, "$2");
        int numberOfClosingBrackets = closingBrackets.split(CLOSING_BRACKET, SPLIT_WITH_EMPTY_ENTRIES).length - 1;

        for (int i = 0; i < numberOfClosingBrackets; i++) {
            currentNode = currentNode.getParent();
        }

        return currentNode;
    }

    private void loadStatementAttributes(AST node, String[] attributesArray) {
        String attributeValue = new String();
        boolean complexAttribute = false;

        for (String attribute : attributesArray) {
            if (!attribute.isEmpty()) {
                if (complexAttribute) {
                    if (attribute.equals("=")) {
                        attributeValue += "\"";
                        complexAttribute = false;

                        attributeValue = attributeValue.replaceAll("^('+|\"+)|(\"+|'+)$", "");

                        node.getAttributes().add(attributeValue);
                        attributeValue = new String();
                    } else if (attribute.endsWith("\"") || attribute.endsWith("'")) {
                        attributeValue += " " + attribute.replaceAll(CLOSING_BRACKET, "");
                        complexAttribute = false;

                        attributeValue = attributeValue.replaceAll("^('+|\"+)|(\"+|'+)$", "");

                        node.getAttributes().add(attributeValue);
                        attributeValue = new String();
                    } else {
                        attributeValue += " " + attribute.replaceAll(CLOSING_BRACKET, "");

                        if (attributeValue.endsWith("\"") || attributeValue.endsWith("'")) {
                            complexAttribute = false;

                            attributeValue = attributeValue.replaceAll("^('+|\"+)|(\"+|'+)$", "");

                            node.getAttributes().add(attributeValue);
                            attributeValue = new String();
                        }
                    }
                } else if ((attribute.startsWith("\"") && !attribute.endsWith("\"")) || (attribute.startsWith("'") && !attribute.endsWith("'"))) {
                    complexAttribute = true;
                    attributeValue = attribute.replaceAll(CLOSING_BRACKET, "");

                    if ((attributeValue.startsWith("\"") && attributeValue.endsWith("\"")) || (attributeValue.startsWith("'") && attributeValue.endsWith("'"))) {
                        complexAttribute = false;

                        attributeValue = attributeValue.replaceAll("^('+|\"+)|(\"+|'+)$", "");

                        node.getAttributes().add(attributeValue);
                        attributeValue = new String();
                    }
                } else {
                    attributeValue = attribute.replaceAll(CLOSING_BRACKET, "");

                    if (attributeValue.startsWith("<")) {
                        attributeValue += ">";
                    }

                    attributeValue = attributeValue.replaceAll("^('+|\"+)|(\"+|'+)$", "");

                    node.getAttributes().add(attributeValue);
                    attributeValue = new String();
                }
            }
        }
    }

    private AST loadFunctionNode(String line, String sourcePath) {
        AST functionNode = new AST();
        List<String> attributes = new ArrayList<>();

        String functionData = line.split(CLOSING_BRACKET)[0];

        loadFunctionData(functionData, attributes);
        loadFunctionAttributes(functionData, attributes);

        Pattern pattern = Pattern.compile("(.*?)(line:\\d+:\\d+)(.*)");
        Matcher matcher = pattern.matcher(line);
        
        if (matcher.find())
        {
            functionNode.setLine(matcher.group(2));
        }
        
        functionNode.setNodeType(COMPOUND_STATEMENT);
        functionNode.setAttributes(attributes);
        functionNode.setSource(sourcePath);

        asts.add(functionNode);

        return functionNode;
    }

    private void loadFunctionAttributes(String functionData, List<String> attributes) {
        String functionAttributes = functionData.split(OPENING_BRACKET, -1)[1];

        String[] functionAttributesArray = functionAttributes.split(",");

        for (String attribute : functionAttributesArray) {
            String[] attributeData = attribute.split(" ");
            String attributeType = new String();
            String attributeName = new String();

            for (int i = 0; i < attributeData.length - 1; i++) {
                attributeType += attributeData[i] + " ";
            }

            attributeType = attributeType.replaceAll(" $", "");

            attributeName = attributeData[attributeData.length - 1];

            attributes.add(attributeType);
            attributes.add(attributeName);
        }
    }

    private void loadFunctionData(String functionData, List<String> attributes) {
        String functionName;
        String returnedType = "";

        String functionTypeAndName = functionData.split(OPENING_BRACKET)[0];
        String[] functionTypeAndNameAttributes = functionTypeAndName.split(" ");

        for (int i = 0; i < functionTypeAndNameAttributes.length - 1; i++) {
            returnedType += functionTypeAndNameAttributes[i] + " ";
        }

        returnedType = returnedType.replaceAll(" $", "");

        functionName = functionTypeAndNameAttributes[functionTypeAndNameAttributes.length - 1];

        attributes.add(returnedType);
        attributes.add(functionName);
    }

    private String getFiles(File source, String files) {
        File[] children = source.listFiles(sourceFileFilter);

        if (children != null) {
            for (File file : children) {
                if (file.isDirectory()) {
                    files = getFiles(file, files);
                } else {
                    files += file.getAbsolutePath() + " ";
                }
            }
        } else {
            files += source.getAbsolutePath() + " ";
        }

        return files;
    }

    private String getFirstLineFromSource(String sourcePath) throws FileNotFoundException, IOException {
        String result = null, line, parsedLine = null;

        FileInputStream fileInput = new FileInputStream(sourcePath);

        DataInputStream dataInput = new DataInputStream(fileInput);

        BufferedReader reader = new BufferedReader(new InputStreamReader(dataInput));

        while ((line = reader.readLine()) != null) {
            parsedLine = line.replaceAll("\\s", "");

            if (!parsedLine.startsWith("*") && !parsedLine.startsWith("/") && !parsedLine.startsWith("#") && !parsedLine.isEmpty()) {
                result =  line.replaceFirst("\\{$", "");;
                break;
            }
        }
        
        reader.close();
        dataInput.close();
        fileInput.close();

        return result;
    }

    private boolean isFunctionLine(String line) {
        boolean result = false;

        if (line.contains(COMPOUND_STATEMENT) && !line.startsWith("(" + COMPOUND_STATEMENT)) {
            result = true;
        }

        return result;
    }

    private boolean isStatementLine(String line) {
        boolean result = false;

        if (line.startsWith("(")) {
            result = true;
        }

        return result;
    }

    private boolean isAdditionalNodeDataLine(String line) {
        boolean result = false;

        if (line.startsWith("0x")) {
            result = true;
        }

        return result;
    }

    private boolean containsClosingBracket(String line) {
        boolean result = false;

        if (line.endsWith(")")) {
            result = true;
        }

        return result;
    }

    private String loadCommand() {
        String result = "";
        
        try {
            result = loadPath();
        } catch (ParserConfigurationException | SAXException | IOException ex) {
            Exceptions.printStackTrace(ex);
        }
        
        return "cmd /C " + result +" -cc1 -ast-dump ";
    }
    
    private String loadPath() throws ParserConfigurationException, SAXException, IOException {
        String result = "";

        DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
        Document doc = docBuilder.parse (new File("config.xml"));
        
        doc.getDocumentElement().normalize();
        
        NodeList cygwinPath = doc.getElementsByTagName("clangPath");
        
        for (int index = 0; index < cygwinPath.getLength(); index++){
            Node cygwinPathNode = cygwinPath.item(index);
            
            if (cygwinPathNode.getNodeType() == Node.ELEMENT_NODE){
                Element cygwinPathElement = (Element) cygwinPathNode;

                result = cygwinPathElement.getFirstChild().getNodeValue();
            }
        }
        
        return result;
    }
}
